﻿#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <assert.h>
// 原题连接：https://leetcode.cn/problems/remove-duplicates-from-sorted-array/
/*
题目描述：
给你一个 升序排列 的数组 nums ，请你 原地 删除重复出现的元素，使每个元素 只出现一次 
，返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。
由于在某些语言中不能改变数组的长度，所以必须将结果放在数组nums的第一部分。更规范地说，
如果在删除重复项之后有 k 个元素，那么 nums 的前 k 个元素应该保存最终结果。
将最终结果插入 nums 的前 k 个位置后返回 k 。
不要使用额外的空间，你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。

示例 1：
输入：nums = [1,1,2]
输出：2, nums = [1,2,_]
解释：函数应该返回新的长度 2 ，并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。

示例 2：
输入：nums = [0,0,1,1,1,2,2,3,3,4]
输出：5, nums = [0,1,2,3,4]
解释：函数应该返回新的长度 5 ， 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。不需要考虑数组中超出新长度后面的元素。
*/

// 方法1——暴力法
/*
思路：
既然题目已经说是升序序列，那就说明相同元素一定是紧挨在一起的，
那我们可以直接遍历数组，当发现nums[i] = nums[i + 1]时，就将i后面的元素全都往前
移一位，并把数组长度减1。因为考虑到nums[i]会有两个或两个以上个元素与它相等，所以我们完成一次
整体移动之后，下一次判断还是要在i的位置进行(也就是i不进行++操作)。直到nums[i] != nums[i + 1]
才能让i++。
*/

// 有了以上分析，那我们写起代码来也就水到渠成了：
int removeDuplicates1(int* nums, int numsSize) {
	assert(nums);
	int i = 0;
	int j = 0;
	for (i = 0; i < numsSize - 1; i++) {
		if (nums[i] == nums[i + 1]) {
			for (j = i; j < numsSize - 1; j++) {
				nums[j] = nums[j + 1];
			}
			numsSize--;
			i--;
		}
	}
	return numsSize;
}
// 时间复杂度：O(n^2)，n为数组元素个数。
// 空间复杂度：O(1)，我们只需要用到常数级的额外空间。

// 方法2——标记值标记法(其实也可以说是双指针)
/*
我们可以设置一个标记值val，目的是标记数组中已经出现过的数字，
初始时我们让val等于nums[0]，并用一个变量n来记录数组中不相同元素的个数，n的初始值为1，
然后遍历数组，当nums[i] == val时，说明是重复出现的val，不做任何操作，
当nums[i] != val时，就将nums[i]写到nums[1]，并将val的值更新为nums[i]，且n++,
当再次发现一个元素不等于val时，就将这个元素写到nums[2]，并将val的值更新为这个元素的值,且n++。
以此类推，当遍历完数组时我们就完成了所有删除。
这样我们最后直接return n即可。
*/

// 有了以上分析，那我们写起代码来也就水到渠成了：
int removeDuplicates2(int* nums, int numsSize) {
	assert(nums);
	int val = nums[0];
	int n = 1;
	int i = 0;
	for (i = 0; i < numsSize; i++) {
		if (nums[i] != val) {
			nums[n] = nums[i];
			val = nums[i];
			n++;
		}
	}
	return n;
}
// 时间复杂度：O(n)，n为数组元素个数，我们只需要遍历一遍数组即可。
// 空间复杂度：O(1)，我们只需要用到常数级的额外空间。

// 方法3——找相同子序列的最后一个
/*
思路：
想一下有什么方法能在一层循环中直接找出数组中不同元素的个数而又不需要借助标记值呢？
其实我们可以通过循环找到一个相同元素的序列中最后被遍历到的那个元素，即当nums[i] != nums[i + 1]
时，nums[i]就是那最后一个元素。把他们一次写入数组的前半部分，最后再返回不同元素的个数即可。
但是对于数组的最后两个元素，则需要进行特殊处理，因为对于最后一个元素，就无法判断nums[i] != nums[i + 1]
若是nums[i] != nums[i + 1] 则我们只是把倒数第二个元素写进了前面，漏掉了最后一个，而若是nums[i] == nums[i + 1]
那我们就会少了一个不同的值。
所以我们应该这样，不管最后一个和倒数第二个元素是否相等，我们都要把最后一个元素写到数组的前面。
*/

// 有了以上分析，那我们写起代码来也就水到渠成了：
int removeDuplicates3(int* nums, int numsSize) {
	assert(nums);
	if (1 == numsSize) {
		return 1;
	}
	int n = 0;
	int i = 0;
	for (i = 0; i < numsSize - 1; i++) {
		if (nums[i] != nums[i + 1]) {
			nums[n] = nums[i];
			n++;
		}
		if (i == numsSize - 2) {
			nums[n] = nums[i + 1];
			n++;
		}
	}
	return n;
}
// 时间复杂度：O(n)，n为数组元素个数。
// 空间复杂度：O(1)，我们只需要用到常数级的额外空间。